/*******************************************************************************
 * JAFFA - Java Application Framework For All - Copyright (C) 2008 JAFFA
 * Development Group
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License (version 2.1 any later).
 * 
 * See http://jaffa.sourceforge.net/site/legal.html for more details.
 ******************************************************************************/

/**
 * Based on Original Work found at
 * http://extjs.com/forum/showthread.php?p=203828#post203828
 * 
 * @author chander, PaulE
 */
Ext.namespace("Ext.ux.grid");

Ext.ux.grid.MultiGroupingView = Ext.extend(Ext.grid.GroupingView, {
	constructor : function(config) {
		Ext.ux.grid.MultiGroupingView.superclass.constructor.apply(this,
				arguments);
		// Added so we can clear cached rows each time the view is refreshed
		this.on("beforerefresh", function() {
			console.debug("MultiGroupingView.beforerefresh: Cleared Row Cache");
			if (this.rowsCache)
				delete this.rowsCache;
		}, this);

	}

	,
	displayEmptyFields : false

	,
	renderRows : function() {
		// alert('renderRows');
		var groupField = this.getGroupField();
		var eg = !!groupField;
		// if they turned off grouping and the last grouped field is hidden
		if (this.hideGroupedColumn) {
			var colIndexes = [];
			if (eg)
				for (var i = 0, len = groupField.length; i < len; ++i) {
					var cidx = this.cm.findColumnIndex(groupField[i]);
					if (cidx >= 0)
						colIndexes.push(cidx);
					else
						console
								.debug("Ignore unknown column : ",
										groupField[i]);
				}
			if (!eg && this.lastGroupField !== undefined) {
				this.mainBody.update('');
				for (var i = 0, len = this.lastGroupField.length; i < len; ++i) {
					var cidx = this.cm.findColumnIndex(this.lastGroupField[i]);
					if (cidx >= 0)
						this.cm.setHidden(cidx, false);
					else
						console.debug("Error unhiding column " + cidx);
				}
				delete this.lastGroupField;
				delete this.lgflen;
			}

			else if (eg && colIndexes.length > 0
					&& this.lastGroupField === undefined) {
				this.lastGroupField = groupField;
				this.lgflen = groupField.length;
				for (var i = 0, len = colIndexes.length; i < len; ++i) {
					this.cm.setHidden(colIndexes[i], true);
				}
			}

			else if (eg
					&& this.lastGroupField !== undefined
					&& (groupField !== this.lastGroupField || this.lgflen != this.lastGroupField.length)) {
				this.mainBody.update('');
				for (var i = 0, len = this.lastGroupField.length; i < len; ++i) {
					var cidx = this.cm.findColumnIndex(this.lastGroupField[i]);
					if (cidx >= 0)
						this.cm.setHidden(cidx, false);
					else
						console.debug("Error unhiding column " + cidx);
				}
				this.lastGroupField = groupField;
				this.lgflen = groupField.length;
				for (var i = 0, len = colIndexes.length; i < len; ++i) {
					this.cm.setHidden(colIndexes[i], true);
				}
			}
		}
		return Ext.ux.grid.MultiGroupingView.superclass.renderRows.apply(this,
				arguments);
	}

	/**
	 * This sets up the toolbar for the grid based on what is grouped It also
	 * iterates over all the rows and figures out where each group should
	 * appeaer The store at this point is already stored based on the groups.
	 */
	,
	doRender : function(cs, rs, ds, startRow, colCount, stripe) {
		// console.debug ("MultiGroupingView.doRender: ",cs, rs, ds, startRow,
		// colCount, stripe);
		var ss = this.grid.getTopToolbar();
		if (rs.length < 1) {
			return '';
		}

		var groupField = this.getGroupField();
		var gfLen = groupField ? groupField.length : 0;

		// Remove all entries already in the toolbar
		for (var hh = 0; hh < ss.items.length - 1; hh++) {
			var tb = ss.items.itemAt(hh);
			Ext.removeNode(Ext.getDom(tb.id));
		}
		console.debug("Toobar size is now ", ss.items.length);

		if (gfLen == 0) {
			ss.insertButton(ss.items.length - 2,
					new Ext.Toolbar.TextItem(this.grid.emptyToolbarText));
			console.debug("MultiGroupingView.doRender: No Groups");
		} else {
			// console.debug("MultiGroupingView.doRender: Set width to",gfLen,"
			// Groups");

			// Add back all entries to toolbar from GroupField[]
			ss.insertButton(ss.items.length - 2,
					new Ext.Toolbar.TextItem("Grouped By:"));
			for (var gfi = 0; gfi < gfLen; gfi++) {
				var t = groupField[gfi];
				if (gfi > 0)
					ss.insertButton(ss.items.length - 2,
							new Ext.Toolbar.Separator());
				var b = new Ext.Toolbar.Button({
							text : this.cm.getColumnHeader(this.cm
									.findColumnIndex(t))
						});
				b.fieldName = t;
				ss.insertButton(ss.items.length - 2, b);
				// console.debug("MultiGroupingView.doRender: Added Group to
				// Toolbar :",this,t,'=',b.text);
			}
		}

		this.enableGrouping = !!groupField;

		if (!this.enableGrouping || this.isUpdating) {
			return Ext.grid.GroupingView.superclass.doRender.apply(this,
					arguments);
		}

		var gstyle = 'width:' + this.getTotalWidth() + ';';
		var gidPrefix = this.grid.getGridEl().id;
		var groups = [], curGroup, i, len, gid;
		var lastvalues = [];
		var added = 0;
		var currGroups = [];

		// Loop through all rows in record set
		for (var i = 0, len = rs.length; i < len; i++) {
			added = 0;
			var rowIndex = startRow + i;
			var r = rs[i];
			var differ = 0;
			var gvalue = [];
			var fieldName;
			var fieldLabel;
			var grpFieldNames = [];
			var grpFieldLabels = [];
			var v;
			var changed = 0;
			var addGroup = [];

			for (var j = 0; j < gfLen; j++) {
				fieldName = groupField[j];
				fieldLabel = this.cm.getColumnHeader(this.cm
						.findColumnIndex(fieldName));
				v = r.data[fieldName];
				if (v) {
					if (i == 0) {
						// First record always starts a new group
						addGroup.push({
									idx : j,
									dataIndex : fieldName,
									header : fieldLabel,
									value : v
								});
						lastvalues[j] = v;
					} else {
						if ((typeof(v) == "object" && (lastvalues[j].toString() != v
								.toString()))
								|| (typeof(v) != "object" && (lastvalues[j] != v))) {
							// This record is not in same group as previous one
							// console.debug("Row ",i," added group. Values
							// differ: prev=",lastvalues[j]," curr=",v);
							addGroup.push({
										idx : j,
										dataIndex : fieldName,
										header : fieldLabel,
										value : v
									});
							lastvalues[j] = v;
							changed = 1;
						} else {
							if (gfLen - 1 == j && changed != 1) {
								// This row is in all the same groups to the
								// previous group
								curGroup.rs.push(r);
								// console.debug("Row ",i," added to current
								// group");
							} else if (changed == 1) {
								// This group has changed because an earlier
								// group changed.
								addGroup.push({
											idx : j,
											dataIndex : fieldName,
											header : fieldLabel,
											value : v
										});
								// console.debug("Row ",i," added group. Higher
								// level group change");
							} else if (j < gfLen - 1) {
								// This is a parent group, and this record is
								// part of this parent so add it
								if (currGroups[fieldName])
									currGroups[fieldName].rs.push(r);
								// else
								// console.error("Missing on row ",i," current
								// group for ",fieldName);
							}
						}
					}
				} else {
					if (this.displayEmptyFields) {
						addGroup.push({
									idx : j,
									dataIndex : fieldName,
									header : fieldLabel,
									value : this.emptyGroupText || '(none)'
								});
					}
				}
			}// for j
			// if(addGroup.length>0) console.debug("Added groups for row=",i,",
			// Groups=",addGroup);

			for (var k = 0; k < addGroup.length; k++) {
				var grp = addGroup[k];
				gid = gidPrefix + '-gp-' + grp.dataIndex + '-'
						+ Ext.util.Format.htmlEncode(grp.value);

				// if state is defined use it, however state is in terms of
				// expanded
				// so negate it, otherwise use the default.
				var isCollapsed = typeof this.state[gid] !== 'undefined'
						? !this.state[gid]
						: this.startCollapsed;
				var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
				var rndr = this.cm.config[this.cm
						.findColumnIndex(grp.dataIndex)].renderer;
				curGroup = {
					group : rndr ? rndr(grp.value) : grp.value,
					groupName : grp.dataIndex,
					gvalue : grp.value,
					text : grp.header,
					groupId : gid,
					startRow : rowIndex,
					rs : [r],
					cls : gcls,
					style : gstyle + 'padding-left:' + (grp.idx * 12) + 'px;'
				};
				currGroups[grp.dataIndex] = curGroup;
				groups.push(curGroup);

				r._groupId = gid; // Associate this row to a group
			}// for k
		}// for i

		// Flag the last groups as incomplete if more rows are available
		// NOTE: this works if the associated store is a
		// MultiGroupingPagingStore!
		for (var gfi = 0; gfi < gfLen; gfi++) {
			var c = currGroups[groupField[gfi]];
			if (this.grid.store.nextKey)
				c.incomplete = true;
			// console.debug("Final Groups are...",c);
		}

		var buf = [];
		var toEnd = 0;
		for (var ilen = 0, len = groups.length; ilen < len; ilen++) {
			toEnd++;
			var g = groups[ilen];
			var leaf = g.groupName == groupField[gfLen - 1]
			this.doMultiGroupStart(buf, g, cs, ds, colCount);
			if (g.rs.length != 0 && leaf)
				buf[buf.length] = Ext.grid.GroupingView.superclass.doRender
						.call(this, cs, g.rs, ds, g.startRow, colCount, stripe);

			if (leaf) {
				var jj;
				var gg = groups[ilen + 1];
				if (gg != null) {
					for (jj = 0; jj < groupField.length; jj++) {
						if (gg.groupName == groupField[jj])
							break;
					}
					toEnd = groupField.length - jj;
				}
				for (var k = 0; k < toEnd; k++) {
					this.doMultiGroupEnd(buf, g, cs, ds, colCount);
				}
				toEnd = jj;
			}
		}
		// Clear cache as rows have just been generated, so old cache must be
		// invalid
		if (this.rowsCache)
			delete this.rowsCache;
		return buf.join('');
	}

	/** Initialize new templates */
	,
	initTemplates : function() {
		Ext.ux.grid.MultiGroupingView.superclass.initTemplates.call(this);

		if (!this.startMultiGroup) {
			this.startMultiGroup = new Ext.XTemplate(
					'<div id="{groupId}" class="x-grid-group {cls}">',
					'<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div>',
					this.groupTextTpl, '</div></div>',
					'<div id="{groupId}-bd" class="x-grid-group-body">');
		}
		this.startMultiGroup.compile();
		this.endMultiGroup = '</div></div>';
	}

	/**
	 * Private - Selects a custom group template if one has been defined
	 */
	,
	doMultiGroupStart : function(buf, g, cs, ds, colCount) {
		var groupName = g.groupName, tpl = null;

		if (this.groupFieldTemplates) {
			tpl = this.groupFieldTemplates[groupName];
			// console.debug("doMultiGroupStart: Template for group ",groupName,
			// tpl);
			if (tpl && typeof(tpl) == 'string') {
				tpl = new Ext.XTemplate(
						'<div id="{groupId}" class="x-grid-group {cls}">',
						'<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div>',
						tpl, '</div></div>',
						'<div id="{groupId}-bd" class="x-grid-group-body">');
				tpl.compile();
				this.groupFieldTemplates[groupName] = tpl;
			}
		}
		if (tpl)
			buf[buf.length] = tpl.apply(g);
		else
			buf[buf.length] = this.startMultiGroup.apply(g);
	}

	,
	doMultiGroupEnd : function(buf, g, cs, ds, colCount) {
		buf[buf.length] = this.endMultiGroup;
	}

	/**
	 * Should return an array of all elements that represent a row, it should
	 * bypass all grouping sections
	 */
	,
	getRows : function() {
		var r = [];
		// This function is called may times, so use a cache if it is available
		if (this.rowsCache) {
			r = this.rowsCache;
			// console.debug('View.getRows: cached');
		} else {
			// console.debug('View.getRows: calculate');
			if (!this.enableGrouping) {
				r = Ext.grid.GroupingView.superclass.getRows.call(this);
			} else {
				var groupField = this.getGroupField();
				var g, gs = this.getGroups();
				// this.getGroups() contains an array of DIVS for the top level
				// groups
				// console.debug("Get Rows", groupField, gs);

				r = this.getRowsFromGroup(r, gs, groupField[groupField.length
								- 1]);
			}
			// Clone the array, but not the objects in it
			if (r.length >= 0) {
				// Don't cache if there is nothing there, as this happens during
				// a refresh
				// @TODO comment this to disble caching, incase of problems
				this.rowsCache = r;
			}// else
			// console.debug("No Rows to Cache!");
		}
		// console.debug("View.getRows: Found ", r.length, " rows",r[0]);
		// console.trace();
		return r;
	}

	/**
	 * Return array of records under a given group
	 * 
	 * @param r
	 *            Record array to append to in the returned object
	 * @param gs
	 *            Grouping Sections, an array of DIV element that represent a
	 *            set of grouped records
	 * @param lsField
	 *            The name of the grouping section we want to count
	 */
	,
	getRowsFromGroup : function(r, gs, lsField) {
		var rx = new RegExp(".*-gp-" + lsField + "-.*");

		// Loop over each section
		for (var i = 0, len = gs.length; i < len; i++) {

			// Get group name for this section
			var groupName = gs[i].id;
			if (rx.test(groupName)) {
				// console.debug(groupName, " matched ", lsField);
				g = gs[i].childNodes[1].childNodes;
				for (var j = 0, jlen = g.length; j < jlen; j++) {
					r[r.length] = g[j];
				}
				// console.debug("Found " + g.length + " rows for group " +
				// lsField);
			} else {
				if (!gs[i].childNodes[1]) {
					console.error("Can't get rowcount for field ", lsField,
							" from ", gs, i);
				} else
					// if its an interim level, each group needs to be traversed
					// as well
					r = this.getRowsFromGroup(r,
							gs[i].childNodes[1].childNodes, lsField);
			}
		}
		return r;
	}

	/**
	 * Override the onLoad, as it always scrolls to the top, we only want to do
	 * this for an initial load or reload. There is a new event registered in
	 * the constructor to do this
	 */
	,
	onLoad : function() {
	}
});